// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Microsoft.TeamFoundation.Migration.Toolkit.Wit
{
    /// <summary>
    /// Field revisions collection; used by the analysis engine to describe which revisions must
    /// be migrated to the opposite side.
    /// </summary>
    class FieldRevisions
    {
        private List<int> m_revisions;                      // Revisions to take
        private Side m_sourceSide;                          // Side the revisions belong to
        private Pair<int> m_conflictingRevision;            // First conflicting revision

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="sharedData">Shared data object</param>
        /// <param name="ids">Ids of work items on both sides</param>
        /// <param name="changes">Changes on both sides</param>
        /// <param name="mapper">Revision mapper</param>
        public FieldRevisions(
            SyncProcess sharedData,
            Pair<FullId> ids,
            Pair<List<FieldChange>> changes,
            RevisionMapper mapper)
        {
            m_revisions = new List<int>();

            FieldValueComparer fc = sharedData.TfsValueComparer;
            WitMasterPolicy policy = sharedData.Session.Policies.FieldConflict;
            Pair<Dictionary<int, int>> sr = mapper.SyncedRevisions;
            bool hasSource = false;
            int i;

            for (i = 0; i < changes.Left.Count && i < changes.Right.Count; i++)
            {
                if (!hasSource)
                {
                    Pair<FieldChange> change = new Pair<FieldChange>(changes.Left[i], changes.Right[i]);

                    //Account for mapped values in comparison
                    object compareVal = change.Left.Value;
                    if (change.Left.FieldDefinition.ValueMap != null)
                    {
                        compareVal = change.Left.FieldDefinition.ValueMap.GetValue(compareVal, sharedData.StoreTypes.Left);
                    }

                    if (!fc.Equals(compareVal, change.Right.Value))
                    {
                        // Values do not match. This may not indicate a conflict if that revision was
                        // migrated; in this case we're looking at the old conflict that had been resolved.
                        int nextRev;

                        if (sr.Left.TryGetValue(change.Left.Revision, out nextRev))
                        {
                            // Skip all revisions on the right that happened before the migration
                            OffsetRevisions(Side.Left, changes, i, nextRev);
                        }
                        else if (sr.Right.TryGetValue(change.Right.Revision, out nextRev))
                        {
                            // Skip all revisions on the left that happened before the migration
                            OffsetRevisions(Side.Right, changes, i, nextRev);
                        }
                        else
                        {
                            if (policy.Reaction == WitConflictReaction.Throw)
                            {
                                // "throw" allows manual resolution, so we should check whether the conflict
                                // has been resolved manually.
                                List<FieldChange> tfsChanges = changes[sharedData.TfsSide];
                                List<FieldChange> otherChanges = changes[!sharedData.TfsSide];

                                //Account for mapped values in comparison
                                compareVal = tfsChanges[tfsChanges.Count - 1].Value;
                                if (change.Left.FieldDefinition.ValueMap != null)
                                {
                                    compareVal = change.Left.FieldDefinition.ValueMap.GetValue(compareVal, SystemType.Tfs);
                                }

                                if (fc.Equals(compareVal, otherChanges[i].Value))
                                {
                                    // The change has been resolved in TFS. It now should accept all values
                                    // from the non-TFS side.
                                    m_sourceSide = !sharedData.TfsSide;
                                    hasSource = true;
                                    continue;
                                }
                                else
                                {
                                    //Account for mapped values in comparison
                                    compareVal = otherChanges[otherChanges.Count - 1].Value;
                                    if (change.Left.FieldDefinition.ValueMap != null)
                                    {
                                        compareVal = change.Left.FieldDefinition.ValueMap.GetValue(compareVal, SystemType.Other);
                                    }

                                    if (fc.Equals(otherChanges[otherChanges.Count - 1].Value, tfsChanges[i].Value))
                                    {
                                        // The change has been resolved in non-TFS system.
                                        m_sourceSide = sharedData.TfsSide;
                                        hasSource = true;
                                        continue;
                                    }
                                }
                            }
                            else
                            {
                                Debug.Assert(policy.Reaction == WitConflictReaction.Master, "Unsupported field conflict policy!");
                                m_sourceSide = policy.MasterSystem == SystemType.Tfs ? sharedData.TfsSide : !sharedData.TfsSide;
                                hasSource = true;
                            }

                            if (!hasSource || policy.Reaction == WitConflictReaction.Master)
                            {
                                // Fire the event
                                Pair<FullRevision> revisions = new Pair<FullRevision>(
                                    new FullRevision(ids.Left, changes.Left[i].Revision),
                                    new FullRevision(ids.Right, changes.Right[i].Revision));

                                sharedData.Session.FireFieldConflict(
                                    sharedData, 
                                    changes.Left[i].Name,
                                    revisions,
                                    new Pair<object>(changes.Left[i].Value, changes.Right[i].Value),
                                    policy.Reaction);

                                if (!hasSource)
                                {
                                    m_conflictingRevision = new Pair<int>(changes.Left[i].Revision, changes.Right[i].Revision);
                                    i = Math.Max(changes.Left.Count, changes.Right.Count);
                                    break;
                                }
                            }
                        }
                    }
                }

                // Allow all revisions
                if (hasSource)
                {
                    m_revisions.Add(changes[m_sourceSide][i].Revision);
                }
            }

            if (!hasSource)
            {
                m_sourceSide = changes.Left.Count > changes.Right.Count ? Side.Left : Side.Right;
            }

            // Process remaining revisions
            for (; i < changes[m_sourceSide].Count; i++)
            {
                m_revisions.Add(changes[m_sourceSide][i].Revision);
            }
        }

        /// <summary>
        /// Gets all revisions that must be taken for the given field.
        /// </summary>
        public List<int> Revisions { get { return m_revisions; } }

        /// <summary>
        /// Gets the side the revisions belong to.
        /// </summary>
        public Side SourceSide { get { return m_sourceSide; } }

        /// <summary>
        /// Returns first conflicting revision for the field.
        /// </summary>
        public Pair<int> ConflictingRevision { get { return m_conflictingRevision; } }

        /// <summary>
        /// Removes revisions on the target side up to the specified revision and removes the
        /// original revision from the source side.
        /// </summary>
        /// <param name="sourceSide">Side source revision belongs to</param>
        /// <param name="changes">List of changes</param>
        /// <param name="index">Index of revision on the source side</param>
        /// <param name="targetRev">Upper revision on the target side</param>
        private static void OffsetRevisions(
            Side sourceSide,
            Pair<List<FieldChange>> changes,
            int index,
            int targetRev)
        {
            // Offset the target side
            Side side = !sourceSide;
            int i = index;

            while (i < changes[side].Count && changes[side][i].Revision <= targetRev)
            {
                i++;
            }

            changes[side].RemoveRange(index, i - index - 1);
        }
    }
}
